<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>M3U 文件管理器</title>
    <!-- <script src="https://cdn.bootcdn.net/ajax/libs/hls.js/1.5.18/hls.min.js"></script> -->
    <!-- <script src="https://cdn.bootcdn.net/ajax/libs/PapaParse/5.4.1/papaparse.min.js"></script> -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.4.1/papaparse.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/hls.js/8.0.0-beta.3/hls.min.js"></script>
    <style>
        /* styles.css */
body {
    font-family: Arial, sans-serif;
    margin: 0;
    padding: 0;
}

.header {
    position: sticky;
    top: 0;
    background-color: white;
    padding: 10px;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
    z-index: 100;
}

.header h1 {
    margin: 0;
    font-size: 24px;
    display: inline-block;
}

#expandBtn {
    padding: 2px 5px;
    font-size: 14px;
    cursor: pointer;
    margin-left: 2em;
    background-color: #2a2ade;
    color: #fff;
}
.controls {
    display: flex;
    gap: 10px;
    margin-top: 10px;
    flex-wrap: wrap;
    align-items: center;
}

.controls button,
.controls select,

.container {
    /* padding: 20px; */
    overflow-x: auto;
    max-height: calc(100vh - 150px);
    /* 限制容器高度 */
}

table {
    width: 100%;
    border-collapse: collapse;
    /* margin-top: 20px; */
    user-select: none;
    table-layout: fixed;
}

thead {
    position: sticky;
    top: -1px;
    background-color: white;
    z-index: 50;
}

th,
td {
    border: 1px solid #ddd;
    /* padding: 8px; */
    text-align: left;
    position: relative;
    overflow: hidden;
    white-space: nowrap;
}
td button.playing {
    background-color: #decc2a;
}
th {
    background-color: #f2f2f2;
    width: 100px;
}

th._select {
    width: 40px;
}
th._play{
    width: 60px;
}
th.title {
    width: 140px;
}
th.url {
    width: 350px;
}

th div.resize-handle{
    position: absolute;
    top: 0px;
    right: 0px;
    width: 5px;
    height: 100%;
    cursor: col-resize;
    background-color: transparent;
}

tr.selected /*,tr.selected input[type="text"]*/ { 
    background-color: #d3e9ff;
}

tr.dragging {
    opacity: 0.5;
}

/* input[type="text"] {
    width: 100%;
    padding: 5px;
    box-sizing: border-box;
    border: none;
} */

#rowCount,
#selectedRowCount {
    margin-left: 10px;
    font-size: 14px;
    color: #555;
}
.sort {
    /* display: inline-block;
    right: 10px;
    position: absolute;
    height: 100%; */

    /* display: inline-block; */
    right: 10px;
    /* position: absolute; */
    height: 100%;
    float: right;
}
.sort:hover {
    cursor: pointer;
    background-color: #f2f2f2;
}
.sort .arrow {
    display: inline-block;
    width: 0;
    height: 0;
    border-left: 5px solid transparent;
    border-right: 5px solid transparent;
    border-bottom: 5px solid #aaa;
    margin-left: 5px;
    vertical-align: middle;
}

.sort.asc .arrow {
    border-bottom-color: #333;
}

.sort.desc .arrow {
    transform: rotate(180deg);
    border-bottom-color: #333;
}

#pasteArea {
    position: fixed;
    top: 0;
    left: 0;
    width: 1px;
    height: 1px;
    /* display: none; */
    overflow: hidden;
}

.hide {
    display: none;
}

#player {
    /* width: 100%; */
    /* max-width: 100%; */
    margin: 0 auto;
    display: none;
}

#player video {
    max-width: 100%;
    max-height: 100%;
    height: auto;
    display: block;
}
#timeoutSeconds{
    width: 50px;
}
.button {
    border-radius: 3px;
    padding: 1px 6px;
    font-size: 14px;
    cursor: pointer;
    background-color: #f3f3f3;
    border: 1px solid #777;
}

.button:hover {
    background-color: #ddd;
    border-color: #555;
}

#maxConcurrent {
    width: 50px;
}
    </style>
</head>

<body>
    <div class="header">
        <div id="player">
            <div>
                <button id="hidePlayer">隐藏播放器</button>
                <button id="resolutionSwitch">播放480p</button>
                <span id="title"></span><span id="playInfo"></span>
            </div>
            <video controls></video>
        </div>
        <h1>M3U 文件管理器</h1>
        <span id="expandBtn">折叠</span><span id="filename"></span>
        <div class="controls show">
            <label for="fileInput" class="button">
                <span>导入</span>
            </label>
            <input type="file" id="fileInput" accept=".m3u,.m3u8,.txt,.csv" multiple style="display:none;">
            <button id="deleteSelected">删除</button>
            <button id="exportM3U">出M3U</button>
            <button id="exportCSV">出CSV</button>
            <select id="groupSelector">
                <!-- 分组选项将动态生成 -->
            </select>
            <button id="moveToGroup">到组</button>
            <span id="rowCounts">行数: 0</span>
            <span id="canplayCount">可用: 0</span>
        </div>
        <div class="controls show">
            <select name="detectCondition" id="detectCondition">
                <option value="blank" selected>空</option>
                <option value="error">解析失败</option>
                <option value="timeout">检测超时</option>
                <option value="error">错误</option>
                <option value="nocanplay">不可用</option>
            </select>
            <input type="text" id="maxConcurrent" placeholder="最大并发数" value="5">
            <input type="checkbox" name="forceHls" id="forceHls">
            <label for="forceHls">强制HLS</label>
            <button id="detectBtn">开始检测</button>
            <span id="progress"></span>
            <div id="pasteArea" contenteditable="true"></div>
            <div>
                <label for="columnSelect">过滤：</label>
                <select id="columnSelect"></select>
                <input type="text" id="filterInput" placeholder="前缀减号表示不包含">
                <button id="clearColumnBtn">清除列内容</button>
            </div>
        </div>
    </div>
    <div class="container">
        <table id="m3uTable">
            <thead>
                <tr>
                    <th class="_select drag-handle show"><input type="checkbox" id="selectAll"></th>
                    <th class="_play">播放</th>
                    <th class="id show">id<div class="sort">
                            <div class="arrow"></div>
                        </div>
                    </th>
                    <th class="name">name<div class="sort">
                            <div class="arrow"></div>
                        </div>
                    </th>
                    <th class="group show">分组<div class="sort">
                            <div class="arrow"></div>
                        </div>
                    </th>
                    <th class="title show">名称<div class="sort">
                            <div class="arrow"></div>
                        </div>
                    </th>
                    <th class="resolution show">分辨率
                        <div class="sort">
                            <div class="arrow"></div>
                        </div>
                    </th>
                    <th class="delay show">延迟ms<div class="sort">
                            <div class="arrow"></div>
                        </div>
                    </th>
                    <th class="lagged show">卡顿<div class="sort">
                            <div class="arrow"></div>
                        </div>
                    </th>
                    <th class="url show">URL<div class="sort">
                            <div class="arrow"></div>
                        </div>
                    </th>
                    <th class="logo show">logo</th>
                </tr>
            </thead>
            <tbody>
                <!-- 动态生成的行将插入到这里 -->
            </tbody>
        </table>
    </div>
    <script>
// script.js

//获取document中有id的所有元素，返回对象，key为id，value为元素
/** @type {Object<string, HTMLElement>} 
 * @property {HTMLElement} fileInput - 文件输入框
 * @property {HTMLElement} m3uTable - m3u文件列表
 * @property {HTMLElement} columnSelect - 列选择下拉框
 * @property {HTMLElement} clearColumnBtn - 清空列按钮
 * @property {HTMLElement} expandBtn - 展开/折叠按钮
 * @property {HTMLElement} pasteArea - 粘贴区域
 * @property {HTMLElement} exportM3U - 导出m3u按钮
 * @property {HTMLElement} exportCSV - 导出csv按钮
 * @property {HTMLElement} forceHls - 强制HLS按钮
 * 
*/

const elementsWithIds = {};

// 获取页面上所有带有 id 属性的元素
const elements = document.querySelectorAll('[id]');

// 遍历这些元素
elements.forEach(element => {
    const id = element.id;
    elementsWithIds[id] = element;
});

const video = document.querySelector('#player video');
let lastSelectedRow = null; // 用于支持 Shift 多选
// const keys=[]
const cols = {};
// const shows= {};
//获取table th 的所有class作为keys
const ths = document.querySelectorAll('#m3uTable th');
ths.forEach(th => {
    const key = th.classList[0];
    if (key && !key.startsWith('_')) {
        cols[key] = {
            show: th.classList.contains('show'),
            title: th.textContent,
        };
        // keys.push(key);
        // cols[key]shows[key] = th.classList.contains('show');
    }
});
function row2obj(row) {
    obj = {};
    Object.keys(cols).forEach((key, index) => {
        obj[key] = row.cells[index + 2].textContent;
    });
    return obj;
}

function obj2row(obj) {
    const row = document.createElement('tr');
    let innerHTML = `
        <td class="show"><input type="checkbox"></td>
        <td><button onclick="play(this)">播放</button></td>`;
    Object.keys(cols).forEach(key => {
        const value = obj[key] || '';
        innerHTML += `
        <td class="editable ${cols[key].show ? 'show' : ''}">${value}</td>`;
    });
    row.innerHTML = innerHTML;
    return row;
}
const clearColumnBtn = document.getElementById('clearColumnBtn');
clearColumnBtn.addEventListener('click', function () {
    const column = document.getElementById('columnSelect').value;
    const rows = getSelectedRows();
    rows.forEach(row => {
        row.cells[Object.keys(cols).indexOf(column) + 2].textContent = '';
    });
});
// //添加清空按钮到th.resolution
// const thResolution = document.querySelector('#m3uTable th.resolution');
// const clearResolutionsBtn = document.createElement('button');
// clearResolutionsBtn.textContent = '清空';
// clearResolutionsBtn.addEventListener('click', function () {
//     const iResolution = document.querySelector('#m3uTable th.resolution').cellIndex;
//     const rows = getSelectedRows();
//     rows.forEach(row => {
//         row.cells[iResolution].textContent = '';
//     });
// });
// thResolution.appendChild(clearResolutionsBtn);

document.querySelector('#expandBtn').addEventListener('click', function (e) {
    if (e.target.textContent === '折叠') {
        e.target.textContent = '展开';
        document.querySelectorAll('.show').forEach(elem => {
            elem.classList.remove('show');
            elem.classList.add('hide');
        });
    } else {
        e.target.textContent = '折叠';
        document.querySelectorAll('.hide').forEach(elem => {
            elem.classList.remove('hide');
            elem.classList.add('show');
        });
    }
});
document.addEventListener('paste', async (e) => {
    if (document.querySelector('td[contenteditable="true"]')) return;
    const div = document.getElementById('pasteArea');
    div.innerHTML = '';
    div.focus();
    setTimeout(() => {
        const content = div.innerText.trim();

        if (content) {
            if (content.startsWith('#EXT')) {
                parseM3U(content, true); // 追加模式
            } else {
                parseTXT(content, true); // 追加模式
            }
        }
    }, 2000);
});
let editCell;
document.querySelector('#m3uTable tbody').addEventListener('click', function (e) {
    if (e.target.classList.contains('editable') && e.target.contentEditable != 'true') {
        if (editCell) editCell.contentEditable = false;
        editCell = e.target;
        editCell.contentEditable = true;
        editCell.focus();
        //光标置于内容末尾
        const range = document.createRange();
        range.selectNodeContents(editCell);
        range.collapse(false);
        window.getSelection().removeAllRanges();
        window.getSelection().addRange(range);
    }
});

document.getElementById('exportM3U').addEventListener('click', function () {
    const rows = document.querySelectorAll('#m3uTable tbody tr');
    let m3uContent = '#EXTM3U x-tvg-url="http://epg.51zmt.top:8000/e.xml.gz,https://epg.112114.xyz/pp.xml.gz"\n';
    rows.forEach(row => {
        const obj = row2obj(row);
        const id = obj.id ? ` tvg-id="${obj.id}"` : '';
        const name = obj.name ? ` tvg-name="${obj.name}"` : '';
        const logo = obj.logo ? ` tvg-logo="${obj.logo}"` : '';
        const group = obj.group ? ` group-title="${obj.group}"` : '';
        const resolution = obj.resolution ? ` tvg-resolution="${obj.resolution}"` : '';
        const title = obj.title ? `${obj.title}` : '';

        m3uContent += `#EXTINF:-1${id}${name}${logo}${group}${resolution},${title}\n${obj.url}\n`;
    });

    const blob = new Blob([m3uContent], { type: 'application/x-mpegurl' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename + '.m3u';
    a.click();
    URL.revokeObjectURL(url);
});

document.getElementById('exportCSV').addEventListener('click', function () {
    const rows = document.querySelectorAll('#m3uTable tbody tr');
    let objs = [];
    rows.forEach(row => {
        const obj = row2obj(row);
        objs.push(obj);
    });
    const csvContent = Papa.unparse(objs);

    const blob = new Blob([csvContent], { type: 'text/csv' });

    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename + '.csv';
    a.click();
    URL.revokeObjectURL(url);
});

document.getElementById('moveToGroup').addEventListener('click', function () {
    const iGroup = Object.keys(cols).indexOf('group') + 2;
    // const iGroup = document.querySelector('#m3uTable th.group').cellIndex;
    const selectedGroup = document.getElementById('groupSelector').value;
    const rows = getSelectedRows();
    rows.forEach(row => {
        row.cells[iGroup].textContent = selectedGroup;
    });
});

let filename = '';
// 文件导入逻辑
document.getElementById('fileInput').addEventListener('change', function (event) {
    // const file = event.target.files[0];
    for (let i = 0; i < event.target.files.length; i++) {
        let file = event.target.files[i];
        if (filename === '') {
            filename = file.name;
            const dotIndex = filename.lastIndexOf('.');
            if (dotIndex > 0) {
                filename = filename.substring(0, dotIndex);
                document.getElementById('filename').textContent = filename;
            }
        }
        const reader = new FileReader();
        reader.onload = function (e) {
            const content = e.target.result;
            const filename = file.name;
            if (filename.endsWith('.m3u') || filename.endsWith('.m3u8')) {
                parseM3U(content, true); // 追加模式
            } else if (filename.endsWith('.txt')) {
                parseTXT(content, true); // 追加模式
            } else if (filename.endsWith('.csv')) {
                parseCSV(content, true); // 追加模式
            }
            updateRowCounts(); // 更新行数
        };
        reader.readAsText(file);
    }
});

const tableBody = document.querySelector('#m3uTable tbody');


function addRow(obj) {
    const row = obj2row(obj);
    row.addEventListener('click', handleRowClick, true);
    tableBody.appendChild(row);
}

function getExistentUrls(appendMode) {
    if (appendMode) {
        return getColumnTextSet('url');
    }
    tableBody.innerHTML = ''; // 非追加模式时清空表格
    return new Set();
}

function parseM3U(content, appendMode = false) {
    const existingUrls = getExistentUrls(appendMode);
    const lines = content.split('\n');

    for (let i = 0; i < lines.length; i++) {
        const line = lines[i].trim();
        if (line.startsWith('#EXTINF:')) {
            const idMatch = line.match(/tvg-id="([^"]+)"/);
            const nameMatch = line.match(/tvg-name="([^"]+)"/);
            const logoMatch = line.match(/tvg-logo="([^"]+)"/);
            const groupMatch = line.match(/group-title="([^"]+)"/);
            const resolutionMatch = line.match(/tvg-resolution="([^"]+)"/);

            const titleMatch = line.match(/,([^,]*)$/);
            const url = lines[i + 1].trim();

            if (titleMatch && !existingUrls.has(url)) { // 去重
                const obj = {
                    id: idMatch ? idMatch[1] : '',
                    name: nameMatch ? nameMatch[1] : '',
                    logo: logoMatch ? logoMatch[1] : '',
                    group: groupMatch ? groupMatch[1] : '未分组', // 如果没有分组信息，则使用默认分组
                    resolution: resolutionMatch ? resolutionMatch[1] : '',
                    title: titleMatch[1],
                    url: url,
                };
                addRow(obj);
                existingUrls.add(url); // 添加到已存在的 URL 集合中
            }
        }
    }
}

function parseTXT(content, appendMode = false) {
    const existingUrls = getExistentUrls(appendMode);
    const lines = content.split('\n');

    for (let i = 0; i < lines.length; i++) {
        const line = lines[i].trim();
        const parts = line.split(',');
        if (parts.length === 2) {
            const title = parts[0].trim();
            const url = parts[1].trim();

            if (!existingUrls.has(url)) { // 去重
                addRow({
                    group: '未分组',
                    title: title,
                    url: url,
                });
                existingUrls.add(url); // 添加到已存在的 URL 集合中
            }
        }
    }
}
function parseCSV(content, appendMode = false) {
    const existingUrls = getExistentUrls(appendMode);
    const objs = Papa.parse(content, { header: true }).data;
    objs.forEach(obj => {
        if (!existingUrls.has(obj.url)) { // 去重
            if (!obj.group) {
                obj.group = '未分组';
            };
            addRow(obj);
            existingUrls.add(url); // 添加到已存在的 URL 集合中
        }
    });
}

function handleRowClick(event) {
    const row = event.target.closest('tr');
    if (!row) return;

    const checkbox = row.querySelector('input[type="checkbox"]');
    if (event.target.tagName !== 'INPUT' && event.target.tagName !== 'BUTTON') {
        if (event.shiftKey && lastSelectedRow) {
            // Shift 多选
            const rows = Array.from(document.querySelectorAll('#m3uTable tbody tr'));
            const startIndex = rows.indexOf(lastSelectedRow);
            const endIndex = rows.indexOf(row);
            const [start, end] = [Math.min(startIndex, endIndex), Math.max(startIndex, endIndex)];
            rows.slice(start, end + 1).forEach(r => {
                const cb = r.querySelector('input[type="checkbox"]');
                cb.checked = true;
                r.classList.add('selected');
            });
        } else if (event.ctrlKey || event.metaKey) {
            // Ctrl 多选
            checkbox.checked = !checkbox.checked;
            row.classList.toggle('selected', checkbox.checked);
        } else {
            // 单选
            document.querySelectorAll('#m3uTable tbody tr').forEach(r => {
                r.classList.remove('selected');
                r.querySelector('input[type="checkbox"]').checked = false;
            });
            checkbox.checked = true;
            row.classList.add('selected');
        }
        lastSelectedRow = row;
        updateRowCounts();
    }
}

document.getElementById('groupSelector').addEventListener('focus', function (e) {
    updateGroupSelector();
});

function getColumnTextSet(thClass) {
    const columnTextSet = new Set();
    const rows = document.querySelectorAll('#m3uTable tbody tr');
    rows.forEach(row => {
        const text = row.cells[Object.keys(cols).indexOf(thClass) + 2].textContent.trim();
        if (text) {
            columnTextSet.add(text);
        };
    });
    return columnTextSet;
}
function updateGroupSelector() {
    const groups = getColumnTextSet('group');
    const groupSelector = document.getElementById('groupSelector');
    groupSelector.innerHTML = '';
    groups.forEach(group => {
        const option = document.createElement('option');
        option.value = group;
        option.textContent = group;
        groupSelector.appendChild(option);
    });
}



let draggingRows = [];



// 列宽调整逻辑
function setupColumnResize() {
    const thElements = document.querySelectorAll('#m3uTable th');
    thElements.forEach(th => {
        const resizeHandle = document.createElement('div');
        resizeHandle.className = 'resize-handle';
        th.appendChild(resizeHandle);

        let isResizing = false;
        let startX, startWidth;

        resizeHandle.addEventListener('mousedown', (e) => {
            isResizing = true;
            startX = e.clientX;
            startWidth = th.offsetWidth;
            document.addEventListener('mousemove', onMouseMove);
            document.addEventListener('mouseup', onMouseUp);
        });

        function onMouseMove(e) {
            if (isResizing) {
                const newWidth = startWidth + (e.clientX - startX);
                th.style.width = `${newWidth}px`;
            }
        }

        function onMouseUp() {
            isResizing = false;
            document.removeEventListener('mousemove', onMouseMove);
            document.removeEventListener('mouseup', onMouseUp);
        }
    });
}

// 初始化拖拽排序
function setupDragAndDrop() {
    const tbody = document.querySelector('#m3uTable tbody');

    tbody.addEventListener('mousedown', (e) => {
        if (e.target.cellIndex != 0) {
            return;
        }
        const row = e.target.closest('tr');
        if (row && row.querySelector('input[type="checkbox"]:checked')) {
            draggingRows = Array.from(document.querySelectorAll('#m3uTable tbody tr input[type="checkbox"]:checked'))
                .map(checkbox => checkbox.closest('tr'));
            draggingRows.forEach(row => row.classList.add('dragging'));
        }
    });

    document.addEventListener('mousemove', (e) => {
        if (draggingRows.length > 0) {
            // e.preventDefault();
            const rows = Array.from(tbody.querySelectorAll('tr'));
            const mouseY = e.clientY;
            const targetRow = rows.find(row => {
                const rect = row.getBoundingClientRect();
                return mouseY < rect.top + rect.height / 2;
            });

            if (targetRow && !draggingRows.includes(targetRow)) {
                const firstDraggingRow = draggingRows[0];
                const targetIndex = rows.indexOf(targetRow);
                const draggingIndex = rows.indexOf(firstDraggingRow);

                if (targetIndex < draggingIndex) {
                    // 向上拖动
                    tbody.insertBefore(firstDraggingRow, targetRow);
                    draggingRows.slice(1).forEach(row => tbody.insertBefore(row, targetRow));
                } else {
                    // 向下拖动
                    draggingRows.slice(1).forEach(row => tbody.insertBefore(row, targetRow));
                    tbody.insertBefore(firstDraggingRow, targetRow);
                }
            }
        }
    });

    document.addEventListener('mouseup', () => {
        if (draggingRows.length > 0) {
            draggingRows.forEach(row => row.classList.remove('dragging'));
            draggingRows = [];
            updateRowCounts();
        }
    });
}

function clickSortButton(e) {
    clickButton = e.currentTarget;
    const table = document.querySelector('#m3uTable');
    const tbody = document.querySelector('#m3uTable tbody');
    const rows = Array.from(tbody.querySelectorAll('tr'));
    const columnIndex = Array.from(table.querySelectorAll('th')).indexOf(clickButton.closest('th'));
    // e.preventDefault();
    document.querySelectorAll('th div.sort').forEach(button => {
        if (button !== clickButton) {
            button.className = 'sort';
        } else {
            if (clickButton.classList.contains('asc')) {
                clickButton.className = 'sort desc';
            } else {
                clickButton.className = 'sort asc';
            }
        }
    });

    rows.sort((a, b) => {
        const valueA = a.cells[columnIndex].textContent.trim().toLowerCase();
        const valueB = b.cells[columnIndex].textContent.trim().toLowerCase();
        // const result = valueA.localeCompare(valueB);
        const result = valueA < valueB ? -1 : 1;
        if (clickButton.classList.contains('desc')) {
            return -result;
        } else {
            return result;
        }
    });

    rows.forEach(row => tbody.appendChild(row));
    updateRowCounts();
}
document.querySelectorAll('div.sort').forEach(sortButton => {
    sortButton.addEventListener('click', clickSortButton);
});



// 全选/取消全选
document.getElementById('selectAll').addEventListener('click', function (event) {
    const checkboxes = document.querySelectorAll('#m3uTable tbody input[type="checkbox"]');
    checkboxes.forEach(checkbox => {
        checkbox.checked = event.target.checked;
        const row = checkbox.closest('tr');
        row.classList.toggle('selected', checkbox.checked);
    });
    updateRowCounts(); // 更新选中行数
});

// 行选择逻辑
document.querySelector('#m3uTable tbody').addEventListener('click', function (event) {
    const checkbox = event.target.closest('input[type="checkbox"]');
    if (checkbox) {
        const row = checkbox.closest('tr');
        row.classList.toggle('selected', checkbox.checked);
        updateRowCounts(); // 更新选中行数
    }
});

// 初始化
setupColumnResize();
setupDragAndDrop();
updateRowCounts();
// updateSelectedRowCount();

document.getElementById('detectBtn').addEventListener('click', toggleDetection);

let maxConcurrent; // 最大并发数
let pendingQueue = [];
let activeCount = 0;
let isPaused = true;
let totalTasks = 0;
let timeout = 2; // 超时时间
// 主控制函数
function toggleDetection() {
    if (isPaused) {
        startDetection();
    } else {
        stopDetection();
    }
}

let startTime = 0;
let canplayCount = 0;
// 启动检测

function getCanPlayCount() {
    const iResolution = document.querySelector('#m3uTable th.resolution').cellIndex;
    const rows = Array.from(document.querySelectorAll(`#m3uTable tbody tr`));
    const canplayCount = rows.filter(row => row.cells[iResolution].textContent.trim().includes('可用')).length;
    document.getElementById('canplayCount').textContent = `可用: ${canplayCount}`;
    return canplayCount;
}

const canplay = {
    m3u8: video.canPlayType('application/vnd.apple.mpegurl') != '',
    mp4: video.canPlayType("video/mp4") != ''
}

async function startDetection() {
    if (Hls === undefined) {
        alert('检测失败: Hls.js 未加载，请重启页面');
        return;
    }

    maxConcurrent = parseInt(document.getElementById('maxConcurrent').value) || 5;
    timeout = prompt('请输入超时时间(单位: 秒)', timeout) || timeout;
    canplayCount = getCanPlayCount();
    // 获取待检测的行
    let rows = [];
    const detectCondition = document.getElementById('detectCondition').value;
    switch (detectCondition) {
        case 'blank':
            rows = Array.from(getSelectedRows())
                .filter(row => !row.cells[Object.keys(cols).indexOf('resolution') + 2].textContent.trim());
            break;
        case 'error':
            rows = Array.from(getSelectedRows())
                .filter(row => row.cells[Object.keys(cols).indexOf('resolution') + 2].textContent.trim().endsWith('解析失败'));
            break;
        case 'timeout':
            rows = Array.from(getSelectedRows())
                .filter(row => row.cells[Object.keys(cols).indexOf('resolution') + 2].textContent.trim().endsWith('检测超时'));
            break;
        case 'error':
            rows = Array.from(getSelectedRows())
                .filter(row => row.cells[Object.keys(cols).indexOf('resolution') + 2].textContent.trim().endsWith('错误'));
            break;
        case 'nocanplay':
            rows = Array.from(getSelectedRows())
                .filter(row => !row.cells[Object.keys(cols).indexOf('resolution') + 2].textContent.trim().endsWith('可用'));
            break;
    }

    if (rows.length === 0) {
        updateProgress('没有需要检测的项目');
        return;
    }
    startTime = Date.now();
    pendingQueue = rows.map(row => ({ row }));
    totalTasks = pendingQueue.length;
    isPaused = false;
    updateButtonState();
    updateProgress();
    processQueue();
}

// 停止检测
function stopDetection() {
    isPaused = true;
    updateButtonState();
    updateProgress('已停止');
}

// 更新按钮状态
function updateButtonState() {
    const btn = document.getElementById('detectBtn');
    if (isPaused) {
        btn.textContent = '开始检测';
        if (activeCount > 0) {
            btn.disabled = true;
        } else {
            btn.disabled = false;
            const newCanplayCount = getCanPlayCount() - canplayCount;
            setTimeout(() => {
                alert(`检测完成, 可用视频增加 ${newCanplayCount} 个.`);
            }, 1000);
        }
    } else {
        btn.textContent = '停止检测';
    }
}

// 更新进度显示
function updateProgress(additionalInfo) {
    const completed = totalTasks - pendingQueue.length - activeCount;
    const progress = document.getElementById('progress');
    let status = `进度: ${completed}/${totalTasks}`;

    if (activeCount > 0) {
        status += ` | 进行中: ${activeCount}`;
    }
    if (additionalInfo) {
        status += ` | ${additionalInfo}`;
    }

    progress.textContent = status;
}

// 队列处理器
async function processQueue() {
    while (!isPaused && activeCount < maxConcurrent && pendingQueue.length > 0) {
        const { row } = pendingQueue.shift();
        activeCount++;
        updateProgress();
        updateButtonState();

        try {
            const iResolution = document.querySelector('#m3uTable th.resolution').cellIndex;
            const resolutionCell = row.cells[iResolution];
            const iDelay = document.querySelector('#m3uTable th.delay').cellIndex;
            const delayCell = row.cells[iDelay];
            const iLagged = document.querySelector('#m3uTable th.lagged').cellIndex;
            const laggedCell = row.cells[iLagged];

            resolutionCell.innerHTML = '<span class="loading">解析分辨率</span>';
            const result = await detectVideo(row);
            resolutionCell.innerHTML = (result.width ? ` ${result.width}x${result.height}` : '') + result.status;
            delayCell.innerHTML = (result.delay ? result.delay : '')
            laggedCell.innerHTML = (result.lagged ? '是' : '');
        } finally {
            activeCount--;
            if (pendingQueue.length === 0 && activeCount === 0) {
                // 所有任务完成
                isPaused = true;
                updateProgress(`检测完成, 用时: ${(Date.now() - startTime) / 1000} 秒`);
            }
            updateButtonState();
            if (!isPaused) processQueue();
        }
    }
}

const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
// 单行处理流程
async function detectVideo(row) {
    const iUrl = document.querySelector('#m3uTable th.url').cellIndex;
    const url = row.cells[iUrl].textContent.trim();
    if (!isTouchDevice) {
        fetched = await fetch('http://192.168.102.126:5000/test?url=' + encodeURIComponent(url));
    }
    return new Promise((resolve) => {
        const result = {};
        const hls = new Hls();
        const video = document.createElement('video');
        video.style.display = 'none';
        video.muted = true;
        document.body.appendChild(video);
        const timeoutId = setTimeout(() => {
            cleanup();
            if (result.status === undefined) {
                result.status = '🟡 检测超时';
            }
            resolve(result);
        }, timeout * 1000);


        const cleanup = () => {
            clearTimeout(timeoutId);
            hls.destroy();
            video.remove();
        };

        video.addEventListener('loadedmetadata', () => {
            result.status = `🟢 可用`;
            result.width = video.videoWidth;
            result.height = video.videoHeight;
        });

        video.addEventListener('resize', () => {
            // result.status = `🟢 可用`;
            result.width = video.videoWidth;
            result.height = video.videoHeight;
        });

        let lastUpdate = Date.now();

        video.addEventListener('canplay', function () {
            if (result.delay === undefined) {
                result.delay = Date.now() - lastUpdate;
                result.lagged = 0;
            }
        });
        video.addEventListener('waiting', function () {
            if (result.lagged === 0)
                result.lagged += 1; // 视频卡顿：缓冲中
        });
        let durationTimeoutId;
        video.addEventListener('durationchange', function () {
            if (result.duration === undefined) {
                result.duration = video.duration;
                durationTimeoutId = setTimeout(() => {
                    cleanup();
                    result.break = true;
                    resolve(result);
                }, video.duration*1000+1000);
            }else if (result.duration - video.duration>5){
                clearTimeout(durationTimeoutId);
                cleanup();
                resolve(result);
            }
        });

        fetch(url, { method: 'HEAD' })
            .then(response => {
                if (!response.ok) {
                    cleanup();
                    resolve({ status: '🟡 响应错误' });
                }
            })
            .catch((e) => {
                console.log(e);
                cleanup();
                resolve({ status: '🟡 链接错误' });
            });
        hls.on(Hls.Events.ERROR, (event, data) => {
            cleanup();
            if (data.fatal) {
                resolve({ status: '🟡 解析失败' });
            }else {
                resolve({ status: '🟡 Hls 错误' });
            }
        });



        try {
            playVideo(url, video, hls);
            // hls.loadSource(url);
            // hls.attachMedia(video);
            // video.play().catch(() => { });
        } catch (e) {
            cleanup();
            resolve({ status: '🟡 初始化失败' });
        }
    });
}


//类似于detectVideo，用于直接播放视频
function updateResult(result) {
    document.getElementById('playInfo').textContent = result.status + (result.width ? ` ${result.width}x${result.height}` : '')
        + (result.delay ? ` 初始缓冲: ${result.delay}ms` : '')
        + (result.lagged ? ` 卡顿${result.lagged}` : '');
}

let result;

video.addEventListener('loadedmetadata', () => {
    result.status = `🟢 可用`;
    result.width = video.videoWidth;
    result.height = video.videoHeight;
    updateResult(result);
});
video.addEventListener('resize', () => {
    // result.status = `🟢 可用`;
    result.width = video.videoWidth;
    result.height = video.videoHeight;
    updateResult(result);
});
let lastUpdate = Date.now();

video.addEventListener('canplay', function () {
    if (result.delay === undefined) {
        result.delay = Date.now() - lastUpdate;
        result.lagged = 0;
        updateResult(result);
    } else {
        result.lagged += 1; // 视频卡顿：播放进度停滞
        updateResult(result);
    }
});


function playVideo(url, video, hls) {
    const urlType = url.endsWith('.mp4') ? 'mp4' : 'm3u8';
    if (document.getElementById('forceHls').checked || !canplay[urlType]) { // 如果浏览器支持直接播放
        hls.loadSource(url);
        hls.attachMedia(video);
        video.play().catch(() => { });
    } else {
        video.src = url;
        video.load();
        video.play().catch(() => { });
    }
}

let currentPlay;
function play(btn) {
    if (currentPlay) {
        currentPlay.classList.remove('playing');
    }
    currentPlay = btn;
    currentPlay.classList.add('playing');
    const row = btn.closest('tr');
    const obj = row2obj(row);
    const url = obj.url;
    result = {};
    updateResult({ status: '' });
    document.getElementById('player').style.display = 'block';
    document.getElementById('title').textContent = obj.title;
    if (Hls === undefined) {
        alert('播放失败: Hls.js 未加载，请重启页面');
        return;
    }
    let hls;
    if (hls) {
        hls.destroy();
    }
    hls = new Hls();
    hls.on(Hls.Events.ERROR, (event, data) => {
        if (data.fatal) {
            updateResult({ status: '🟡 解析失败' });
        }
    });

    lastUpdate = Date.now();
    try {
        playVideo(url, video, hls);
        // hls.loadSource(url);
        // hls.attachMedia(video);
        // video.play().catch(() => { });
    } catch (e) {
        updateResult({ status: '🟡 初始化失败' });
    }
}


document.getElementById('resolutionSwitch').addEventListener('click', function (e) {
    const button = e.target;
    const video = document.querySelector('#player video');
    if (button.textContent === '播放480p') {
        button.textContent = '播放原尺寸';
        video.style.height = '480px';
    } else {
        button.textContent = '播放480p';
        video.style.height = '';
    }
});
document.getElementById('hidePlayer').addEventListener('click', function () {
    document.getElementById('player').style.display = 'none';
    currentPlay.classList.remove('playing');
    currentPlay = null;
    video.pause();
    video.src = '';
});


// 监听全屏变化事件
document.addEventListener('fullscreenchange', handleFullscreenChange);
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
document.addEventListener('mozfullscreenchange', handleFullscreenChange);
document.addEventListener('MSFullscreenChange', handleFullscreenChange);

function handleFullscreenChange() {
    if (isFullscreen()) {
        // 进入全屏时，尝试横屏
        screen.orientation.lock('landscape').catch(() => {
            console.log('横屏锁定失败');
        });
    } else {
        // 退出全屏时，恢复竖屏
        screen.orientation.unlock();
    }
}

function isFullscreen() {
    return document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
}

const columnSelect = document.getElementById('columnSelect');

const colOptions = Object.keys(cols).map(col => {
    return `<option value="${col}">${cols[col].title}</option>`
}).join('')

columnSelect.innerHTML = colOptions;
columnSelect.value = 'resolution'

document.getElementById('filterInput').addEventListener('input', filterTable);

function filterTable() {
    const columnSelect = document.getElementById('columnSelect').value;
    let filterText = document.getElementById('filterInput').value.trim().toLowerCase();
    let isNot = false;
    if (filterText.startsWith('-')) {
        isNot = true;
        filterText = filterText.slice(1);
    }
    const rows = document.querySelectorAll('#m3uTable tbody tr');
    rows.forEach(row => {
        const text = row.cells[Object.keys(cols).indexOf(columnSelect) + 2].textContent.trim().toLowerCase();
        if (isNot && text.includes(filterText)) {
            row.style.display = 'none';
        } else if (!isNot && !text.includes(filterText)) {
            row.style.display = 'none';
        } else {
            row.style.display = '';
        }
    });
    updateRowCounts();
}

//获取显示行中已经选择的行，没有选择则获取所有显示行，返回行数组
function getSelectedRows() {
    const rows = document.querySelectorAll('#m3uTable tbody tr:not([style*="display: none"])');
    const selectedRows = Array.from(rows).filter(row => row.classList.contains('selected'));
    return selectedRows.length > 0 ? selectedRows : rows;
}
// 删除选中行
document.getElementById('deleteSelected').addEventListener('click', function () {
    const selectedRows = getSelectedRows();
    selectedRows.forEach(row => row.remove());
    updateRowCounts(); // 更新行数
});

// 显示总行数、筛选行数、选择行数
function updateRowCounts() {
    const totalRows = document.querySelectorAll('#m3uTable tbody tr').length;
    const filterRows = document.querySelectorAll('#m3uTable tbody tr:not([style*="display: none"])');
    const selectedRows = Array.from(filterRows).filter(row => row.classList.contains('selected')).length;
    document.getElementById('rowCounts').textContent = `行数: ${totalRows}/${filterRows.length}/${selectedRows}`;
    getCanPlayCount();
}

    </script>
</body>

</html>